#include "StdAfx.h"
#define _USE_MATH_DEFINES
#include <math.h>
#include "Synthesizer.h"
#include "ToneInstrument.h"
#include "Instrument.h"
#include "xmlhelp.h"
#include <algorithm>
#include <string>

using namespace std;

CSynthesizer::CSynthesizer(void)
{
	CoInitialize(NULL);

	mChannels = 2;
	mSampleRate = 44100;
	mSamplePeriod = 1 / mSampleRate;

	mBpm = 120;
	mSecperbeat = 0.5;
	mBeatspermeasure = 4;

	// Create wavetables for saw, square, and triangle waves
	mSawwave = CreateSaw();
	mSquarewave = CreateSquare();
	mTrianglewave = CreateTriangle();

	//mWaveInstFactory.LoadFile("./sounds/drumriff.wav");
}


CSynthesizer::~CSynthesizer(void)
{
}

//! Start the synthesizer
void CSynthesizer::Start( void )
{
	mInstruments.clear();
	mCurrentNote = 0;
	mMeasure = 0;
	mBeat = 0;
	mTime = 0;

}


//! Generate one audio frame
bool CSynthesizer::Generate( double *aFrame )
{
	//
    // Phase 1: Determine if any notes need to be played.
    //
    
	while( mCurrentNote < (int)mNotes.size())
	{
		// Get a pointer to the current note
		CNote *lNote = &mNotes[mCurrentNote];

		// If the measure is in the future we can't play
		// this note just yet.
		if(lNote->Measure() > mMeasure )
			break;

		// If this is the current measure, but the
        // beat has not been reached, we can't play
        // this note.
        if(lNote->Measure() == mMeasure && lNote->Beat() > mBeat)
            break;

        //
        // Play the note!
        //

        // Create the instrument object
        CInstrument *lInstrument = NULL;
        if(lNote->Instrument() == L"ToneInstrument")
        {
            lInstrument = new CToneInstrument();
        }
		else if(lNote->Instrument() == L"Wave")
		{
			mWaveInstFactory.SetNote( lNote );
			lInstrument = mWaveInstFactory.CreateInstrument();
		}
		else if(lNote->Instrument() == L"DrumKit")
		{
			mDrumFactory.SetNote( lNote );
			lInstrument = mDrumFactory.CreateInstrument();
		}
		else if(lNote->Instrument() == L"Subtractive")
		{
			mSubtractiveFactory.SetNote( lNote );
			lInstrument = mSubtractiveFactory.CreateInstrument(mSawwave, mSquarewave, mTrianglewave);
		}
		else if (lNote->Instrument() == L"Additive")
		{
			mAdditiveFactory.SetSampleRate( GetSampleRate() );
			mAdditiveFactory.SetNote( lNote );
			lInstrument = mAdditiveFactory.CreateInstrument();
		}
        // Configure the instrument object
        if(lInstrument != NULL)
        {
            lInstrument->SetSampleRate(GetSampleRate());
			lInstrument->SetNote(lNote);
			lInstrument->SetBeatsPerMinute(mBpm);
            lInstrument->Start();

            mInstruments.push_back(lInstrument);
        }

        mCurrentNote++;
	}
	
	//
    // Phase 2: Clear all channels to silence 
    //

    for(int c=0;  c<GetNumChannels();  c++)
    {
        aFrame[c] = 0;
    }

    //
    // Phase 3: Play an active instruments
    //

    //
    // We have a list of active (playing) instruments.  We iterate over 
    // that list.  For each instrument we call generate, then add the
    // output to our output frame.  If an instrument is done (Generate()
    // returns false), we remove it from the list.
    //

    for(list<CInstrument *>::iterator node = mInstruments.begin();  node!=mInstruments.end();  )
    {
        // Since we may be removing an item from the list, we need to know in 
        // advance, what is after it in the list.  We keep that node as "next"
        list<CInstrument *>::iterator next = node;
        next++;

        // Get a pointer to the allocated instrument
        CInstrument *lInstrument = *node;

        // Call the generate function
        if(lInstrument->Generate())
        {
            // If we returned true, we have a valid sample.  Add it 
            // to the frame.
            for(int c=0;  c<GetNumChannels();  c++)
            {
                aFrame[c] += lInstrument->Frame(c);
            }
        }
        else
        {
            // If we returned false, the instrument is done.  Remove it
            // from the list and delete it from memory.
            mInstruments.erase(node);
            delete lInstrument;
        }

        // Move to the next instrument in the list
        node = next;
    }

	//
	// Phase 4: Advance the time and beats
	//

	// Time advances by the sample period
	mTime += GetSamplePeriod();

    // Beat advances by the sample period divided by the 
    // number of seconds per beat.  The inverse of seconds
    // per beat is beats per second.
	mBeat += GetSamplePeriod() / mSecperbeat;

    // When the measure is complete, we move to
    // a new measure.  We might be a fraction into
    // the new measure, so we subtract out rather 
    // than just setting to zero.
	if( mBeat > mBeatspermeasure )
	{
		mBeat -= mBeatspermeasure;
		mMeasure++;
	}
    //
    // Phase 5: Determine when we are done
    //

    // We are done when there is nothing to play.  We'll put something more 
    // complex here later.
    return !mInstruments.empty() || mCurrentNote < (int)mNotes.size();
}

void CSynthesizer::Clear( void )
{
	mNotes.clear();
	mInstruments.clear();
}

void CSynthesizer::OpenScore(CString & aFileName)
{
    Clear();

    //
    // Create an XML document
    //

    CComPtr<IXMLDOMDocument>  pXMLDoc;
    bool succeeded = SUCCEEDED(CoCreateInstance(CLSID_DOMDocument, NULL, CLSCTX_INPROC_SERVER,  
                               IID_IXMLDOMDocument, (void**)&pXMLDoc));
    if(!succeeded)
    {
        AfxMessageBox(L"Failed to create an XML document to use");
	    return;
    }

    // Open the XML document
    VARIANT_BOOL ok;
    succeeded = SUCCEEDED(pXMLDoc->load(CComVariant(aFileName), &ok));
    if(!succeeded || ok == VARIANT_FALSE)
    {
        AfxMessageBox(L"Failed to open XML score file");
        return;
    }

    //
    // Traverse the XML document in memory!!!!
    // Top level tag is <score>
    //

    CComPtr<IXMLDOMNode> node;
    pXMLDoc->get_firstChild(&node);
    for( ; node != NULL;  NextNode(node))
    {
        // Get the name of the node
        CComBSTR nodeName;
        node->get_nodeName(&nodeName);
		if(nodeName == "score")
        {
            XmlLoadScore(node);
        }
    }

	sort( mNotes.begin(), mNotes.end());
}

void CSynthesizer::XmlLoadScore(IXMLDOMNode * aXml)
{
	// Get a list of all attribute nodes and the
    // length of that list
    CComPtr<IXMLDOMNamedNodeMap> lAttributes;
    aXml->get_attributes(&lAttributes);
    long lLen;
    lAttributes->get_length(&lLen);

    // Loop over the list of attributes
    for(int i=0;  i<lLen;  i++)
    {
        // Get attribute i
        CComPtr<IXMLDOMNode> lAttrib;
        lAttributes->get_item(i, &lAttrib);

        // Get the name of the attribute
        CComBSTR lName;
        lAttrib->get_nodeName(&lName);

        // Get the value of the attribute.  A CComVariant is a variable
        // that can have any type. It loads the attribute value as a
        // string (UNICODE), but we can then change it to an integer 
        // (VT_I4) or double (VT_R8) using the ChangeType function 
        // and then read its integer or double value from a member variable.
        CComVariant lValue;
        lAttrib->get_nodeValue(&lValue);

        if(lName == L"bpm")
        {
            lValue.ChangeType(VT_R8);
            mBpm = lValue.dblVal;
            mSecperbeat = 1 / (mBpm / 60);
        }
        else if(lName == L"beatspermeasure")
        {
            lValue.ChangeType(VT_I4);
            mBeatspermeasure = lValue.intVal;
        }

    }


    CComPtr<IXMLDOMNode> lNode;
    aXml->get_firstChild(&lNode);
    for( ; lNode != NULL;  NextNode(lNode))
    {
        // Get the name of the node
        CComBSTR lName;
        lNode->get_nodeName(&lName);
		if(lName == L"instrument")
        {
            XmlLoadInstrument(lNode);
        }
    }

}

void CSynthesizer::XmlLoadInstrument(IXMLDOMNode * xml)
{
	wstring instrument = L"";

    // Get a list of all attribute nodes and the
    // length of that list
    CComPtr<IXMLDOMNamedNodeMap> attributes;
    xml->get_attributes(&attributes);
    long len;
    attributes->get_length(&len);

    // Loop over the list of attributes
    for(int i=0;  i<len;  i++)
    {
        // Get attribute i
        CComPtr<IXMLDOMNode> attrib;
        attributes->get_item(i, &attrib);

        // Get the name of the attribute
        CComBSTR name;
        attrib->get_nodeName(&name);

        // Get the value of the attribute.  
        CComVariant value;
        attrib->get_nodeValue(&value);

        if(name == "instrument")
        {
            instrument = value.bstrVal;
        }
    }

    
    CComPtr<IXMLDOMNode> node;
    xml->get_firstChild(&node);
    for( ; node != NULL;  NextNode(node))
    {
        // Get the name of the node
        CComBSTR name;
        node->get_nodeName(&name);

        if(name == L"note")
        {
           XmlLoadNote(node, instrument);
        }
    }
}
void CSynthesizer::XmlLoadNote(IXMLDOMNode * xml, std::wstring & instrument)	
{
	mNotes.push_back( CNote() );
	mNotes.back().XmlLoad(xml, instrument);
	
}
std::vector<double> CSynthesizer::CreateSaw()
{
	std::vector<double> wavetable;
	int cycle_size = int((44100.0 / 440.0) + 0.5);
	wavetable.resize(cycle_size);


	int p = 0;
	double time = 0.0;
	int i = 0;
	double sampleRate = 44100.0;
	double freq1 = 440.0;
	double multiplier = -1.0;
	int j = 0;
	double amp = 1.0;

	for(double time=0.;  time < cycle_size;  time += 1. , i++)
	{   
		double s = 0; 
		for(int h = 1; h < (sampleRate/(2.0 * freq1)); h++)
		{
				s += sin(2.0 * 3.14 * double(h) * freq1 * time) / (double(h*h));
		}
		double sample = amp*s;
		wavetable[i] = sample;
	}
	return wavetable;

}
std::vector<double> CSynthesizer::CreateSquare()
{
	std::vector<double> wavetable;
	int cycle_size = int((44100.0 / 440.0) + 0.5);
	wavetable.resize(cycle_size);


	int p = 0;
	double time = 0.0;
	int i = 0;
	double sampleRate = 44100.0;
	double freq1 = 440.0;
	double multiplier = -1.0;
	int j = 0;
	double amp = 1.0;

	for(double time=0.;  time < cycle_size;  time += 1. , i++)
	{   
		double s = 0; 
		for(int h = 1; h < (sampleRate/(2.0 * freq1)); h++)
		{
			if(h % 2 == 1)
			{
				s += sin(2.0 * 3.14 * h * freq1 * time) / (h*h);
			}
		}
		double sample = amp*s;
		wavetable[i] = sample;
	}
	return wavetable;

}
std::vector<double> CSynthesizer::CreateTriangle()
{
	std::vector<double> wavetable;
	int cycle_size = int((44100.0 / 440.0) + 0.5);
	wavetable.resize(cycle_size);


	int p = 0;
	double time = 0.0;
	int i = 0;
	double sampleRate = 44100.0;
	double freq1 = 440.0;
	double multiplier = -1.0;
	int j = 0;
	double amp = 1.0;

	for(double time=0.;  time < cycle_size;  time += 1. , i++)
	{   
		double s = 0; 
		for(int h = 1; h < (sampleRate/(2.0 * freq1)); h++)
		{
			if(h % 2 == 1)
			{
				s += sin(2.0 * 3.14 * h * freq1 * time) / (h*h);
				if(j % 2 == 1)
				{
					s = s * multiplier;
				}
				j++;
			}
		}
		double sample = amp*s;
		wavetable[i] = sample;
	}
	return wavetable;
}